本篇同步發表於我的 Hashnode 部落格:
Eva Chen | 網頁設計師下班後 (hashnode.dev)
↓ 今日學習重點 ↓
使用 Media Queries 的 prefers-color-scheme
搭配 CSS 變數設定預設的淺色/深色模式使用 HTML input checkbox 做淺色與深色模式的切換
關於顏色的設定方法、顏色的變數、混色的新方法,我們在前兩篇都說過了,關於顏色還有什麼需要了解的呢?那就是近幾年在軟體設計中常出現的:淺色與深色模式切換。
今天我們要學會使用 CSS Media Queries 加上 CSS 變數打造自己的淺色與深色模式,甚至更進一步,用 HTML 的 input checkbox 做到簡單的切換(toggle)行為。
另外,雖然 SASS (SCSS) 與 JS 不在本系列學習範圍內,但是還是提供了他們的解法給大家參考。
prefers-color-scheme
在 CSS 中我們可以透過 Media Queries 的 prefers-color-scheme
偵測使用者的裝置是偏好淺色還是深色模式。語法很簡單,總共有 3 個值可以使用:
no-preference
:當使用者裝置偏好色系不知道時
light
:當使用者裝置偏好淺色模式時
dark
:當使用者裝置偏好深色模式時
寫起來如下:
@media (prefers-color-scheme: no-preference) { ... }
@media (prefers-color-scheme: light) { ... }
@media (prefers-color-scheme: dark) { ... }
接著,我們可以使用 CSS 的變數,設定淺色模式時的 11 個黑白灰階層(由淺至深,含黑白色),然後,再另外於深色模式時將變數依序顛倒設定(由深至淺,含黑白色),覆蓋開頭設定的灰階變數。
關於 CSS 變數的的寫法,可參考這系列之前的文章:
#09 原生的 CSS 變數,基本與進階應用
CSS
/* -------- 預設 -------- */
:root {
--gray-000: #fff;
--gray-100: #f9f9fd;
--gray-200: #e8edf4;
--gray-300: #DBE2E7;
--gray-400: #CED7E0;
--gray-500: #A9B3BD;
--gray-600: #677889;
--gray-700: #495562;
--gray-800: #2D4155;
--gray-900: #1A2632;
--gray-1000: #000;
}
/* -------- 當使用者偏好深色模式 -------- */
@media (prefers-color-scheme: dark){
:root{
--gray-000: #000;
--gray-100: #1A2632;
--gray-200: #2D4155;
--gray-300: #495562;
--gray-400: #677889;
--gray-500: #A9B3BD;
--gray-600: #CED7E0;
--gray-700: #DBE2E7;
--gray-800: #e8edf4;
--gray-900: #f9f9fd;
--gray-1000: #fff;
}
}
補個設計小知識,以上範例的黑白灰,在色相上都帶有一點點的藍色。
因為如果是純正的黑白灰,沒有一點顏色的話,看起來會很像影印的結果,讓人感覺死氣沉沉。所以在 UI 設計上,常常見到黑白灰會加上一點藍色或紫色調,呈現結果會比較漂亮。當然這不是絕對,也是有很棒的純黑白設計。
這樣一來,只要需要切換顏色的地方都套用我們設定的黑白灰變數,就能夠依據使用者偏好色系,呈現對應的的淺色/深色模式囉!
DEMO 連結:@media prefers-color-scheme
不過,這樣還不夠,我們通常會希望:預設顏色模式是依據使用者偏好色系,但是使用者也可以自主切換淺色/深色模式。
其實 HTML 的 input checkbox 加上 CSS 的 :checked
偽類,能夠在不寫 JS 的情況下,做到簡單的來回切換(toggle)行為。
如果換頁時要記住使用者的設定,就需要使用到 JS,可以在 cookie 中存放一個布林值,然後在每一頁套用在這個 input 上。
HTML
<input type="checkbox" id="color-scheme-toggle">
<!-- 使用 label 控制 checkbox -->
<label for="color-scheme-toggle">
<span class="material-icons"></span>
</label>
CSS
/* 隱藏起 checkbox,並且防止誤觸 */
#color-scheme-toggle{
position: fixed;
pointer-events: none;
width: 0;
height: 0;
opacity: 0;
}
label[for="color-scheme-toggle"] {
/* 設定基本樣式 */
display: inline-block;
margin-top: 2rem;
padding: 1rem 1.5rem;
color: var(--gray-700);
cursor: pointer;
border: 1px solid currentcolor;
border-radius: 2rem;
transition: var(--transition-base);
/* 因為想要使用 CSS 切換文字,所以使用 ::before、::after 放文字 */
.material-icons::before{
content: "light_mode";
}
&::after{
content: "Switch to Light Theme";
}
}
接著,透過 checkbox 的勾選與否,加上使用父層選擇器 :has
,設定當有勾選的時候 html 變數不一樣,把 :root
預設的變數覆蓋過去,就能改變整頁的設定了:
注意:Firefox 桌機版即將在下個版本(版本 121)才支援
:has
,而 Firefox Android 尚未支援,開發時請斟酌一下用戶瀏覽器的支援程度。(2023/11)
/* -------- 預設 -------- */
:root {
/* 略:設定淺色變數與文字樣式 */
}
/* 當 html 中的 #color-scheme-toggle checked 時 */
html:has(#color-scheme-toggle:checked) {
/* 略:設定深色變數與文字樣式 */
}
/* -------- 當使用者偏好深色模式 -------- */
@media (prefers-color-scheme: dark) {
/* 略:設定深色變數與文字樣式 */
/* 當 html 中的 #color-scheme-toggle checked 時 */
html:has(#color-scheme-toggle:checked) {
/* 略:設定淺色變數與文字樣式 */
}
}
實際結果結果如下:
DEMO 連結:Pure CSS Color Scheme Toggle
SASS (SCSS) 不在本次系列文的教學範圍內,不會的可以先跳過。
剛剛那樣寫有一點髒髒的,我們重複寫了兩遍長長的淺色與深色的變數與文字樣式,如果你會使用 SASS (SCSS) 可以用 @mixin
和迴圈 @for
管理:
$grays: #fff, #f9f9fd, #e8edf4, #DBE2E7, #CED7E0, #A9B3BD, #677889, #495562, #2D4155, #1A2632, #000;
@mixin light-mode() {
body {
@for $i from 1 through length($grays) {
$color: nth($grays, $i);
--gray-#{$i - 1}00: #{$color};
}
}
label[for="color-scheme-toggle"]{
.material-icons::before{ content: "dark_mode"; }
&::after{ content: "Switch to Dark Theme"; }
}
}
@mixin dark-mode() {
body {
@for $i from 1 through length($grays) {
$color: nth($grays, $i);
--gray-#{10 - $i + 1}00: #{$color};
}
}
label[for="color-scheme-toggle"]{
.material-icons::before{ content: "light_mode"; }
&::after{ content: "Switch to Light Theme"; }
}
}
// -------- 預設 -------- //
@include light-mode();
html:has(#color-scheme-toggle:checked) {
@include dark-mode();
}
// -------- 當使用者偏好深色模式 -------- //
@media (prefers-color-scheme: dark) {
@include dark-mode();
html:has(#color-scheme-toggle:checked) {
@include light-mode();
}
}
JS 不在本次系列文的教學範圍內,不會的可以先跳過。
當然,我們也可以使用 JS 切換樣式。
這邊不管是淺色還是深色模式,CSS 預設的變數寫在 :root
裡,相反的變數則寫成 class,要切換時使用 JS 在 HTML body 加上對應的 class,把 :root
預設的變數覆蓋過去。
我本來使用 JS 切換,是想要減少重複寫 CSS 變數的次數,但是 @media
和其他選擇器不能並列寫在一起,所以還是得要多寫一次深色模式的變數。所以,說有優化其實也還好,就當多一種寫法給大家參考囉!如果你有更好的方式也歡迎底下留言告訴我。
(PS. 這邊的範例沒有 watch 系統預設偏好色系的改變,所以如果更換系統預設偏好色系,要重整才有作用)
CSS
/* -------- 預設 -------- */
:root, .light-mode { /* 略:設定淺色變數 */ }
/* -------- 當使用者偏好深色模式 -------- */
@media (prefers-color-scheme: dark) {
:root { /* 略:設定深色變數 */ }
}
.dark-mode { /* 略:設定深色變數 */ }
JS
const body = document.body;
const toggleIcon = document.getElementById("toggle-icon");
const toggleText = document.getElementById("toggle-text");
const iconString = ["light_mode", "dark_mode"];
const textString = ["Switch to Light Theme", "Switch to Dark Theme"];
// 套用初始文字
if (window.matchMedia('(prefers-color-scheme: dark)').matches){
toggleIcon.innerText = iconString[0];
toggleText.innerText = textString[0];
} else {
toggleIcon.innerText = iconString[1];
toggleText.innerText = textString[1];
}
function toggleString() {
toggleIcon.innerText = (toggleIcon.innerText === iconString[0]) ? iconString[1] : iconString[0];
toggleText.innerText = (toggleText.innerText === textString[0]) ? textString[1] : textString[0];
}
function toggleColorMode() {
if(window.matchMedia('(prefers-color-scheme: dark)').matches){
body.classList.toggle("light-mode");
} else {
body.classList.toggle("dark-mode");
}
toggleString();
}
DEMO 連結:Color Scheme Toggle
好的,關於 CSS 顏色的部分就暫時先告一個段落了。
接下來,我們要來準備處理圖片與畫圖形了。
如果你喜歡我的創作,還想看看其他有趣的分享與日常,
可以追蹤我的 IG @im1010ioio,或者是🧋送杯珍奶鼓勵我,謝謝你🥰。